﻿//---------------------------------------------------------------------------
// <summary>
// Representation of the data InfoPath stores in the CurrentFormData array
// </summary>
//---------------------------------------------------------------------------

namespace InfoPathExtractionRules
{
    using System;
    using System.Collections.Generic;
    using System.Text.RegularExpressions;

    /// <summary>
    /// Enumerates the indexes of items in g_objCurrentFormData javascript array.
    /// </summary>
    public enum CurrentFormDataIndex
    {
        /// <summary>
        /// Index of the PostBack Counter item.
        /// </summary>
        PostBackCounter = 2,

        /// <summary>
        /// Index of the Editing Session ID item.
        /// </summary>
        EditingSessionId = 3,

        /// <summary>
        /// Index of the Solution ID item.
        /// </summary>
        SolutionId = 4,

        /// <summary>
        /// Index of the Session State item.
        /// </summary>
        SessionState = 14,

        /// <summary>
        /// Index of the Canary Value item.
        /// </summary>
        CanaryValue = 37,

        /// <summary>
        /// Number of items in the g_objCurrentFormData array.
        /// </summary>
        NumberOfItems = 38
    }

    /// <summary>
    /// Represents the current state of a form instance.
    /// IPFS does this in a browser using a javascript array.
    /// </summary>
    public class CurrentFormData
    {
        /// <summary>
        /// Name of the context parameter used when extracting current form data.
        /// </summary>
        public const string ContextParameterName = "CurrentFormData";

        /// <summary>
        /// Name of the variable from the browser's javascript array declaration.
        /// </summary>
        private string arrayName;

        /// <summary>
        /// Holds the data from the CurrentFormData javascript array.
        /// </summary>
        private string[] data = new string[(int)CurrentFormDataIndex.NumberOfItems];

        /// <summary>
        /// Initializes a new instance of the CurrentFormData class.
        /// </summary>
        /// <param name="javascriptArray">String that is between the brackets [...] of a CurrentFormData javascript array.</param>
        /// <param name="javascriptArrayName">Name of the javascript array variable.</param>
        public CurrentFormData(string javascriptArray, string javascriptArrayName)
        {
            this.InitWithJavascriptArray(javascriptArray);
            this.ArrayName = javascriptArrayName;
        }

        /// <summary>
        /// Initializes a new instance of the CurrentFormData class.
        /// </summary>
        /// <param name="javascriptArray">String that is between the brackets [...] of a CurrentFormData javascript array.</param>
        public CurrentFormData(string javascriptArray)
        {
            this.InitWithJavascriptArray(javascriptArray);
        }

        /// <summary>
        /// Gets or sets ArrayName: Name of the variable from the browser's javascript array declaration.
        /// </summary>
        /// <value>
        /// Name of the variable from the browser's javascript array declaration.
        /// </value>
        public string ArrayName
        {
            get { return this.arrayName; }
            set { this.arrayName = value; }
        }

        /// <summary>
        /// Gets or sets the number of times the form has performed postbacks.
        /// </summary>
        /// <value>
        /// Number of times the form has performed postbacks.
        /// </value>
        public string PostBackCounter
        {
            get { return this.data[(int)CurrentFormDataIndex.PostBackCounter]; }
            set { this.data[(int)CurrentFormDataIndex.PostBackCounter] = value; }
        }

        /// <summary>
        /// Gets or sets the key that identifies a browser's session.
        /// </summary>
        /// <value>
        /// Key that identifies a browser's session.
        /// </value>
        public string EditingSessionId
        {
            get { return this.data[(int)CurrentFormDataIndex.EditingSessionId]; }
            set { this.data[(int)CurrentFormDataIndex.EditingSessionId] = value; }
        }

        /// <summary>
        /// Gets or sets the key that identifies the session state.
        /// </summary>
        /// <value>
        /// Key that identifies the session state.
        /// </value>
        public string SessionState
        {
            get { return this.data[(int)CurrentFormDataIndex.SessionState]; }
            set { this.data[(int)CurrentFormDataIndex.SessionState] = value; }
        }

        /// <summary>
        /// Gets or sets the key that identifies the form template that was opened.
        /// </summary>
        /// <value>
        /// Key that identifies the form template that was opened.
        /// </value>
        public string SolutionId
        {
            get { return this.data[(int)CurrentFormDataIndex.SolutionId]; }
            set { this.data[(int)CurrentFormDataIndex.SolutionId] = value; }
        }

        /// <summary>
        /// Gets or sets the canary value for security mitigation for playback attacks.
        /// This value changes after every postback.
        /// </summary>
        /// <value>
        /// Security mitigation for playback attacks.
        /// This value changes after every postback.
        /// </value>
        public string Canary
        {
            get { return Regex.Unescape(this.data[(int)CurrentFormDataIndex.CanaryValue]); }
            set { this.data[(int)CurrentFormDataIndex.CanaryValue] = value; }
        }

        /// <summary>
        /// Initializes this object with the items from a javascript array.
        /// </summary>
        /// <param name="var">String that is between the brackets [...] of a CurrentFormData javascript array.</param>
        private void InitWithJavascriptArray(string var)
        {
            int startIndex = 0;
            int itemIndex = 0;
            int nesting = 0;
            char c;

            // Parse the items in the array
            // The items in the array are separated by commas,
            // but the array can contain other arrays which can contain commas
            for (int i = 0; i < var.Length; i++)
            {
                c = var[i];

                // Take into account arrays within this array
                if (c == '[')
                {
                    nesting++;
                }
                else if (c == ']')
                {
                    nesting--;
                }

                // If we are not in an internal array and
                // we reached a comma or the end of the string,
                // then we parsed an item
                if (nesting == 0 && (c == ',' || i == var.Length - 1))
                {
                    string item = IPExtractionUtility.ParseSubstring(var, startIndex, i);
                    this.data[itemIndex] = item.Trim(',', '\"');

                    startIndex = i + 1;
                    itemIndex++;
                }
            }

            if (itemIndex != (int)CurrentFormDataIndex.NumberOfItems)
            {
                string errorMessage =
                    string.Format(
                    System.Globalization.CultureInfo.CurrentCulture,
                    "Javascript array had {0} items, but expected {1}",
                    itemIndex,
                    (int)CurrentFormDataIndex.NumberOfItems);
                throw new ArgumentException(errorMessage);
            }
        }
    }
}
